/* audit-keygen.c
 * 生成 SM2 私钥、自签证书，并打包为 PKCS#12 文件。
 */
//版本 --- 解决audit-keygen自动生成p12_pass
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>       // for PATH_MAX
#include <linux/limits.h> // 有些系统需要
#include <sys/ioctl.h>
#include <linux/fs.h>

#include <openssl/evp.h>
#include <openssl/ec.h>
#include <openssl/x509.h>
#include <openssl/pkcs12.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/objects.h>
#include <locale.h>
#include <libintl.h>


#ifndef GETTEXT_PACKAGE
#define GETTEXT_PACKAGE "audit-gm"
#endif
#ifndef LOCALEDIR
#define LOCALEDIR "/usr/local/share/locale"
#endif
#ifndef _
#define _(s) gettext(s)
#endif
#ifndef N_
#define N_(s) (s)
#endif

#define SALT_BYTES 16
#define DERIVE_BYTES 32
#define DERIVE_ITERATIONS 200000 /* 可调整；越高越安全但越慢 */


static void i18n_init(void) {
    bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    textdomain(GETTEXT_PACKAGE);
}

static void cleanup_openssl(void)
{
    /* OpenSSL 1.1+ 会自动清理，大多数调用不需要手动清理全局 */
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    EVP_cleanup();
#endif
}


static int fs_check_immutable(const char *path) {
    int fd = open(path, O_RDONLY | O_NOFOLLOW);
    if (fd < 0) { perror("open for GETFLAGS"); return -1; }
    int flags = 0;
    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) != 0) { perror("FS_IOC_GETFLAGS"); close(fd); return -1; }
    close(fd);
    return (flags & FS_IMMUTABLE_FL) ? 1 : 0;
}

static int fs_set_immutable(const char *path, int enable)
{
    int fd = open(path, O_RDONLY | O_NOFOLLOW);
    if (fd < 0) { perror("open for SETFLAGS"); return -1; }

    int flags = 0;
    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) != 0) {
        perror("FS_IOC_GETFLAGS");
        close(fd);
        return -1;
    }

    int newflags = enable ? (flags | FS_IMMUTABLE_FL) : (flags & ~FS_IMMUTABLE_FL);
    if (newflags == flags) {  // 已是期望状态
        close(fd);
    } else {
        if (ioctl(fd, FS_IOC_SETFLAGS, &newflags) != 0) {
            perror("FS_IOC_SETFLAGS");
            close(fd);
            return -1;
        }
        close(fd);
    }

    int st = fs_check_immutable(path);
    if (st < 0) return -1;
    if (enable && st != 1) {
        fprintf(stderr, "[ERROR] immutable flag didn't stick on %s\n", path);
        return -1;
    }
    if (!enable && st == 1) {
        fprintf(stderr, "[ERROR] immutable flag still set on %s\n", path);
        return -1;
    }
    return 0;
}


static void hexdump(const unsigned char *buf, size_t len, char *out, size_t outlen)
{
    static const char hex[] = "0123456789abcdef";
    size_t i;
    if (outlen < (len * 2 + 1))
        return;
    for (i = 0; i < len; i++)
    {
        out[i * 2] = hex[(buf[i] >> 4) & 0xF];
        out[i * 2 + 1] = hex[buf[i] & 0xF];
    }
    out[len * 2] = '\0';
}

/* 写盐（hex）并设置权限 0400 */
static int write_salt_file(const char *path, const unsigned char *salt, size_t slen)
{
    char hex[SALT_BYTES * 2 + 1];
    hexdump(salt, slen, hex, sizeof(hex));
    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0400);
    if (fd < 0)
        return -1;
    ssize_t w = write(fd, hex, strlen(hex));
    if (w != (ssize_t)strlen(hex))
    {
        close(fd);
        return -1;
    }
    write(fd, "\n", 1);
    close(fd);
    return 0;
}

/* 从十六进制字符串读回 salt（如果需要反向） */
static int read_salt_hexfile(const char *path, unsigned char *out, size_t outlen)
{
    FILE *f = fopen(path, "r");
    if (!f)
        return -1;
    char buf[128];
    if (!fgets(buf, sizeof(buf), f))
    {
        fclose(f);
        return -1;
    }
    fclose(f);
    size_t len = strlen(buf);
    while (len && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
        buf[--len] = '\0';
    if (len / 2 != outlen)
        return -1;
    for (size_t i = 0; i < outlen; i++)
    {
        unsigned int hi, lo;
        if (sscanf(&buf[i * 2], "%1x%1x", &hi, &lo) != 2)
            return -1;
        out[i] = (hi << 4) | lo;
    }
    return 0;
}

/* 派生口令：优先使用 SM3 HMAC 的 PBKDF2（如果可用），否则回退到 SHA256 */
static int derive_passphrase(const char *secret, const unsigned char *salt, int saltlen,
                             int iterations, unsigned char *out, int outlen)
{
    const EVP_MD *md = EVP_get_digestbyname("sm3");
    if (!md)
    {
        
        fprintf(stderr, _("[WARN] SM3 not available; falling back to SHA256 for PBKDF2.\n"));
        md = EVP_sha256();
    }
    else
    {
        fprintf(stderr, _("[INFO] Using SM3 as PBKDF2 hash.\n"));
    }
    if (!PKCS5_PBKDF2_HMAC(secret, strlen(secret), salt, saltlen, iterations, md, outlen, out))
    {
        return -1;
    }
    return 0;
}

/* 生成 SM2私钥并创建自签 X509 证书 */
static int gen_key_and_cert(EVP_PKEY **out_pkey, X509 **out_cert, const char *cn)
{
    int ret = -1;
    EVP_PKEY *pkey = NULL;
    X509 *x = NULL;

    int nid = NID_sm2;
    if (nid == NID_undef)
    {
        fprintf(stderr, _("[WARN] SM2 curve not available in OpenSSL; cannot generate GM key.\n"));
        return -1;
        // nid = NID_X9_62_prime256v1;
    }
    else
    {
        fprintf(stderr, _("[INFO] Generating key using SM2 curve.\n"));
    }

    EC_KEY *ec = EC_KEY_new_by_curve_name(nid);
    if (!ec)
    {
        fprintf(stderr, _("[ERROR] EC_KEY_new_by_curve_name failed\n"));
        goto done;
    }
    if (!EC_KEY_generate_key(ec))
    {
        fprintf(stderr, _("[ERROR] EC_KEY_generate_key failed\n"));
        goto done;
    }

    pkey = EVP_PKEY_new();
    if (!pkey)
    {
        EC_KEY_free(ec);
        goto done;
    }
    if (!EVP_PKEY_assign_EC_KEY(pkey, ec))
    {
        EC_KEY_free(ec);
        goto done;
    }
    /* ec 被转交至 pkey 对象，后面不要再次 free ec */

    /* 生成自签证书 */
    x = X509_new();
    if (!x)
        goto done;
    ASN1_INTEGER_set(X509_get_serialNumber(x), (long)time(NULL));
    X509_gmtime_adj(X509_get_notBefore(x), 0);
    X509_gmtime_adj(X509_get_notAfter(x), 365 * 24 * 3600); /* 1 year */

    X509_set_pubkey(x, pkey);

    X509_NAME *name = X509_get_subject_name(x);
    if (!name)
        goto done;
    if (!X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
                                    (unsigned char *)cn, -1, -1, 0))
    {
        fprintf(stderr, _("[ERROR] Failed to set certificate CN\n"));
        goto done;
    }
    /* issuer = subject (self-signed) */
    X509_set_issuer_name(x, name);

    /* 强制将密钥标记为 SM2 */
    //EVP_PKEY_set_alias_type(pkey, EVP_PKEY_SM2);

    const EVP_MD *md = EVP_get_digestbyname("sm3");
    if (!md)
    {
        fprintf(stderr, _("[ERROR] SM3 digest not available; cannot sign GM certificate.\n"));
        goto done;
    }
    fprintf(stderr, _("[INFO] Signing certificate with SM3 + SM2.\n"));

    EVP_MD_CTX *mctx = EVP_MD_CTX_new();
    if (!mctx)
    {
        fprintf(stderr, _("[ERROR] EVP_MD_CTX_new failed\n"));
        goto done;
    }

    EVP_PKEY_CTX *pctx = NULL;
    if (EVP_DigestSignInit(mctx, &pctx, md, NULL, pkey) != 1)
    {
        fprintf(stderr, _("[ERROR] EVP_DigestSignInit failed: %s\n"), ERR_error_string(ERR_get_error(), NULL));
        EVP_MD_CTX_free(mctx);
        goto done;
    }

    if (EVP_PKEY_CTX_ctrl_str(pctx, "digest", "sm3") <= 0)
    {
        fprintf(stderr, _("[WARN] Cannot set digest=sm3; OpenSSL may not support this parameter.\n"));
    }
    /* 可选：设置 SM2 ID */
    // if (EVP_PKEY_CTX_ctrl_str(pctx, "sm2_id", "1234567812345678") <= 0)
    // {
    //     fprintf(stderr, _("[WARN] Cannot set SM2 ID; OpenSSL may not support this parameter.\n"));
    // }

    if (X509_sign_ctx(x, mctx) == 0)
    {
        fprintf(stderr, _("[ERROR] X509_sign_ctx failed: %s\n"), ERR_error_string(ERR_get_error(), NULL));
        EVP_MD_CTX_free(mctx);
        goto done;
    }
    EVP_MD_CTX_free(mctx);

    *out_pkey = pkey;
    *out_cert = x;
    ret = 0;

done:
    if (ret != 0)
    {
        if (pkey)
            EVP_PKEY_free(pkey);
        if (x)
            X509_free(x);
    }
    return ret;
}

int main(int argc, char **argv)
{
    /* -------- 解析 --lang=xx 参数；默认强制英文/C -------- */
    const char *opt_lang = NULL;

    /* 预扫描 --lang，其他参数后面再扫 */
    for (int i = 1; i < argc; ++i) {
        if (strncmp(argv[i], "--lang=", 7) == 0) {
            opt_lang = argv[i] + 7;
        } else if (strcmp(argv[i], "--lang") == 0 && i + 1 < argc) {
            opt_lang = argv[i + 1];
        }
    }

    /* 默认：强制英文（不受外部环境影响） */
    setenv("LANGUAGE", "C", 1);
    setlocale(LC_ALL, "C");

    /* 如果指定了 --lang 且不是 en/C，则尝试切换为该语言 */
    if (opt_lang && *opt_lang &&
        strcasecmp(opt_lang, "en") != 0 &&
        strcasecmp(opt_lang, "C")  != 0) {

        /* 优先尝试用户给的 */
        if (!setlocale(LC_ALL, opt_lang)) {
            /* 再试 *.UTF-8 形式 */
            char buf[128];
            snprintf(buf, sizeof(buf), "%s.UTF-8", opt_lang);
            if (!setlocale(LC_ALL, buf)) {
                /* 最后让系统自己决定（可能走 LANG/LC_* 环境） */
                setlocale(LC_ALL, "");
            }
        }
        setenv("LANGUAGE", opt_lang, 1);
    }

    /* 绑定域和目录（必须在 setlocale 之后） */
    i18n_init();


    const char *out_p12 = "/etc/pki/audit/sm2.p12";
    const char *salt_file = "/etc/pki/audit/p12_salt";
    const char *cn = "audit";
    int iterations = DERIVE_ITERATIONS;

    /* 解析简单参数 */
    for (int i = 1; i < argc; i++)
    {
        if (strcmp(argv[i], "--out") == 0 && i + 1 < argc)
        {
            out_p12 = argv[++i];
        }
        else if (strcmp(argv[i], "--salt") == 0 && i + 1 < argc)
        {
            salt_file = argv[++i];
        }
        else if (strcmp(argv[i], "--cn") == 0 && i + 1 < argc)
        {
            cn = argv[++i];
        }
        else if (strcmp(argv[i], "--iter") == 0 && i + 1 < argc)
        {
            iterations = atoi(argv[++i]);
        }
        else if (strncmp(argv[i], "--lang=", 7) == 0)
        {
            continue;
        }
        else if (strcmp(argv[i], "--lang") == 0 && i + 1 < argc)
        {
            ++i;
            continue;
        }
        else
        {
            fprintf(stderr, _("Usage: %s [--out <p12file>] [--salt <saltfile>] [--cn <commonname>] [--iter N] [--lang LANG]\n"), argv[0]);
            return 1;
        }
    }

    /* 确保目录存在 */
    char dirbuf[PATH_MAX];
    strncpy(dirbuf, salt_file, sizeof(dirbuf));
    char *slash = strrchr(dirbuf, '/');
    if (slash)
    {
        *slash = '\0';
        if (strlen(dirbuf) > 0)
            mkdir(dirbuf, 0700);
    }

    /* 读取/生成 salt */
    unsigned char salt[SALT_BYTES];
    if (access(salt_file, R_OK) == 0)
    {
        if (read_salt_hexfile(salt_file, salt, SALT_BYTES) != 0)
        {
            fprintf(stderr, _("[ERROR] Failed to read salt: %s\n"), salt_file);
            return 1;
        }
        fprintf(stderr, _("[INFO] Loaded salt from %s\n"), salt_file);
    }
    else
    {
        if (RAND_bytes(salt, sizeof(salt)) != 1)
        {
            fprintf(stderr, "RAND_bytes failed\n");
            return 1;
        }
        if (write_salt_file(salt_file, salt, sizeof(salt)) != 0)
        {
            fprintf(stderr, _("[ERROR] Failed to write salt file: %s\n"), salt_file);
            return 1;
        }
        /* 设置权限 0400 */
        chmod(salt_file, 0400);
        fprintf(stderr, _("[INFO] Generated and wrote salt to %s\n"), salt_file);
    }

    /* 交互式获取管理员口令（不回显） */
    //char *secret = getpass(_("[PROMPT] Enter master passphrase (will not echo): "));
    //if (!secret || strlen(secret) == 0)

    char *tmp1 = getpass(_("[PROMPT] Enter master passphrase (will not echo): "));
    char *secret = tmp1 ? strndup(tmp1, 1024) : NULL;   // 立刻复制
    if (!secret || strlen(secret) == 0) 
    {
        fprintf(stderr, _("[ERROR] Empty passphrase\n"));
        return 1;
    }

    // char *secret2 = getpass(_("[PROMPT] Confirm passphrase: "));
    // if (!secret2 || strcmp(secret, secret2) != 0)

    char *tmp2 = getpass(_("[PROMPT] Confirm passphrase: "));
    char *secret2 = tmp2 ? strndup(tmp2, 1024) : NULL;
     if (!secret2 || strcmp(secret, secret2) != 0)
    {
        fprintf(stderr, _("[ERROR] Passphrase mismatch\n"));
        if (secret)  { OPENSSL_cleanse(secret,  strlen(secret));  free(secret); }
        if (secret2) { OPENSSL_cleanse(secret2, strlen(secret2)); free(secret2); }
        return 1;
    }

    /* 派生用于 P12 的密钥（转为 hex 字符串作为 p12 密码） */
    unsigned char derived[DERIVE_BYTES];
    if (derive_passphrase(secret, salt, sizeof(salt), iterations, derived, sizeof(derived)) != 0)
    {
        fprintf(stderr, _("[ERROR] Failed to derive passphrase\n"));
        return 1;
    }
    char passhex[DERIVE_BYTES * 2 + 1];
    hexdump(derived, sizeof(derived), passhex, sizeof(passhex));
    
    /* 清理 secret 内存（不是绝对安全） */
    //memset(secret, 0, strlen(secret));
    //memset(secret2, 0, strlen(secret2));

    OPENSSL_cleanse(secret,  strlen(secret));
    OPENSSL_cleanse(secret2, strlen(secret2));

    free(secret);
    free(secret2);

    const char *pass_file = "/etc/pki/audit/p12_pass";
    int fd_pass = open(pass_file, O_WRONLY | O_CREAT | O_TRUNC, 0400);
    if (fd_pass < 0) {
        perror("open p12_pass");
    } else {
        ssize_t w = write(fd_pass, passhex, strlen(passhex));
        if (w != (ssize_t)strlen(passhex)) {
            fprintf(stderr, _("[ERROR] Failed to write p12_pass\n"));
        }
        write(fd_pass, "\n", 1);
        close(fd_pass);
        fprintf(stderr, _("[INFO] Wrote derived passphrase to %s\n"), pass_file);
    }
    
    /* 生成 key + cert */
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();

    EVP_PKEY *pkey = NULL;
    X509 *cert = NULL;
    if (gen_key_and_cert(&pkey, &cert, cn) != 0)
    {
        fprintf(stderr, _("[ERROR] Failed to generate key/certificate\n"));
        cleanup_openssl();
        return 1;
    }

    /* 创建 PKCS#12 */
    PKCS12 *p12 = PKCS12_create(passhex, "audit-key", pkey, cert, NULL, 0, 0, 0, 0, 0);
    if (!p12)
    {
        fprintf(stderr, _("[ERROR] PKCS12_create failed: %s\n"), ERR_error_string(ERR_get_error(), NULL));
        EVP_PKEY_free(pkey);
        X509_free(cert);
        cleanup_openssl();
        return 1;
    }

    /* 写入文件 */
    FILE *f = fopen(out_p12, "wb");
    if (!f)
    {
        perror("fopen");
        PKCS12_free(p12);
        EVP_PKEY_free(pkey);
        X509_free(cert);
        cleanup_openssl();
        return 1;
    }
    if (!i2d_PKCS12_fp(f, p12))
    {
        fprintf(stderr, _("[ERROR] Failed to write PKCS#12 file\n"));
        fclose(f);
        PKCS12_free(p12);
        EVP_PKEY_free(pkey);
        X509_free(cert);
        cleanup_openssl();
        return 1;
    }
    fclose(f);
    /* 权限 0400 */
    chmod(out_p12, 0400);
    
    if (fs_set_immutable("/etc/pki/audit/p12_salt", 1) != 0)
        fprintf(stderr, _("[ERROR] set +i failed on p12_salt\n"));
    if (fs_set_immutable("/etc/pki/audit/p12_pass", 1) != 0)
        fprintf(stderr, _("[ERROR] set +i failed on p12_pass\n"));
    if (fs_set_immutable(out_p12, 1) != 0)
        fprintf(stderr, _("[ERROR] set +i failed on %s\n"), out_p12);


    fprintf(stderr, _("[OK] Generated PKCS#12: %s (salt: %s)\n"), out_p12, salt_file);
    /* 释放 */
    PKCS12_free(p12);
    EVP_PKEY_free(pkey);
    X509_free(cert);
    cleanup_openssl();
    return 0;
}
